//==============================================================================
//
//          DSocket - the socket wrapper classes in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-13 17:14:32 $ $Revision: 1.21 $

//==============================================================================

#include "ofc/DSocket.h"

#ifdef WIN32
#include <ws2tcpip.h>
#else
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#endif
#include <string.h>


#ifndef O_NONBLOCK
#define O_NONBLOCK (O_NDELAY)
#endif


#ifdef WIN32
#define ERRNO      (WSAGetLastError())
#else
#define ERRNO      (errno)
#endif



#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DData.h"
#include "ofc/DSocketAddress.h"

#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#endif


//// Socket types
#define DSK_STREAM     (SOCK_STREAM)     // Stream socket 
#define DSK_DGRAM      (SOCK_DGRAM)      // Datagram socket

//// Send/Receive flags
#ifdef  MSG_OOB
#define DSK_MSG_OOB    (MSG_OOB)         // Out of band message
#endif
#ifdef  MSG_DONTROUTE
#define DSK_MSG_DONTROUTE (MSG_DONTROUTE) // Do not route the message
#endif
#ifdef  MSG_PEEK
#define DSK_MSG_PEEK   (MSG_PEEK)        // Peek data
#endif
#ifdef  MSG_WAITALL
#define DSK_MSG_WAITALL (MSG_WAITALL)    // Wait for the full message
#endif

//// Shutdown flags
#ifdef WIN32
#define DSK_SHUT_RD    (SD_RECEIVE)      // Shutdown receive
#define DSK_SHUT_WR    (SD_SEND)         // Shutdown send
#define DSK_SHUT_RDWR  (SD_BOTH)         // Shutdown both send and receive
#else
#define DSK_SHUT_RD    (SHUT_RD)         // Shutdown receive
#define DSK_SHUT_WR    (SHUT_WR)         // Shutdown send
#define DSK_SHUT_RDWR  (SHUT_RDWR)       // Shutdown both send and receive
#endif


//
//
// The DSocket class implements a number of methods for using network sockets.
//
// @example
// #include <stdio.h>
// #include "ofc/DSocket.h"
// #include "ofc/DSocketAddress.h"
// #include "ofc/DData.h"
// 
// int main(int argc, char *argv[])
// {
//   DSocket            *client = [DSocket new];
//   DInetSocketAddress *addr   = [DInetSocketAddress new];
// 
//                                        // Setup a client
//   [addr loopback :7000];               // Setup an address to localhost, port 7000
// 
//   printf("Open the socket ..\n");
//   [client open :[addr family] :DSK_STREAM :[DSocket protocol :"tcp"]]; // Open the socket for tcp
// 
//   [client reuseAddr :YES];             // Socketoption: Reuse the socket
// 
//   printf("Wait for connection with the server..\n");
//   if ([client connect :addr])          // Try to connect to the server
//   {
//     DData *received;
// 
//     printf("Send data to the server..\n");
//     if (![client send :"Hello there .." :14 :0]) // Send data to server
//       printf("Error sending data to server:%d\n", [client error]);
// 
//     printf("Receive data from the server..\n");
//     received = [client recv :256 :0];            // Receive data from server
// 
//     if (received != nil)
//     {
//       printf("Length of received data:%ld\n", [received length]);
// 
//       [received free];
//     }
//     else
//       printf("Error receiving data from server:%d\n", [client error]);
// 
//     printf("Close the connection..\n");
//     [client shutdown :DSK_SHUT_RDWR];  // Shutdown connection
// 
//     [client close];                    // Close socket
//   }
//   else
//     printf("No connection to the server:%d\n", [client error]);
// 
//   [client free];                       // Cleanup
//   [addr   free];
// 
//   return 0;
// }
// 

@interface DSocket : DObject
{
@private
  int                     _socket;     // the socket
  id <DSocketAddressable> _address;    // the address (reference)
  int                     _family;     // the socket family (DSA_AF_INET,DSA_AF_UNIX)
  int                     _type;       // the socket type
  int                     _protocol;   // the socket protocol 
  BOOL                    _blocking;   // is the socket blocking ?
  int                     _errno;      // the last error
}

#endif


#if _PRIVATE_

#ifndef WIN32
  #ifndef MSG_NOSIGNAL                   // FreeBSD doesn't have MSG_NOSIGNAL..
    #define MSG_NOSIGNAL (0)  
    #ifdef SO_NOSIGPIPE                  // .. but should have SO_NOSIGPIPE
      #define USE_NOSIGPIPE (1)
    #else
      #warning "No support for MSG_NOSIGNAL and SO_NOSIGPIPE"
    #endif
  #endif
#endif

#endif


@implementation DSocket


#if _PRIVATE_

// Keep track of the number of instances

static int _instances = 0;


#endif


//// Constructors

//
// Initialise the socket
//
// @return the object
//

- (DSocket *) init
{
  [super init];

  if (_instances == 0)
  {
#ifdef WIN32
    WSADATA data;
    
    if (WSAStartup(MAKEWORD(1,1), &data) != 0)
    {
      WARNING(DW_UNEXPECTED_ERROR, "Unable to load WinSock");
    }
#endif
  }
  _instances++;

  _socket   = -1;
  _blocking = YES;
  _errno    = 0;
  _family   = DSA_AF_INET;
  _address  = nil;
  
  return self;
}

//
// Initialise the socket with an already opened socket file descriptor
//
// @param fileno    the opened socket file descriptor
// @param address   the address for the socket file descriptor
// @param type      the socket type (DSK_STREAM, DSK_DGRAM)
// @param protocol  the protocol
//
// @return the object
//

- (DSocket *) init :(int) fileno :(id <DSocketAddressable>) address :(int) type :(int) protocol
{
  [self init];

  _socket   = fileno;
  _address  = address;
  _family   = [address family];
  _type     = type;
  _protocol = protocol;

  return self;
}

//
// Initialise the socket with a family, type and protocol
// 
// @param family    the socket family (DSA_AF_INET, DSA_AF_UNIX)
// @param type      the socket type (DSK_STREAM, DSK_DGRAM)
// @param protocol  the protocol
//
// @return the object
//

- (DSocket *) init :(int) family :(int) type :(int) protocol
{
  [self init];
  
  [self open :family :type :protocol];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
  
  return [super shallowCopy];
}


//// Deconstructor

//
// Free the socket object
//
// @return the object
//

- free
{
  if (_socket != -1)
  {
    [self close];
  }
  
  if (_instances > 0)
  {
    _instances--;
    
    if (_instances == 0)
    {
#ifdef WIN32
      if (WSACleanup() == SOCKET_ERROR)
      {
        if (WSAGetLastError() == WSAEINPROGRESS)
        {
          WSACancelBlockingCall();
          WSACleanup();
        }
      }
#endif
    }
  }
  
  return [super free];
}


//// Class methods

//
// Determine the protocol for a protocol name
// 
// @param name      the name of the protocol
// 
// @return the protocol number (or -1 if not found)
// 

+ (int) protocol :(const char *) name
{
  struct protoent  *entry = NULL;
  
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else
  {
    entry = getprotobyname(name);
  }
  
  return (entry != NULL) ? entry->p_proto : -1;
}


//// Member methods

//
// Get the sockets file descriptor
// 
// @return the file descriptor (or -1)
// 

- (int) fileno
{
  return _socket;
}

//
// Get the last error
// 
// @return the last error number
// 

- (int) error
{
  return _errno;
}

//
// Get the socket family
// 
// @return the family (DSA_AF_INET, DSA_AF_UNIX, DSA_AF_INET6)
- (int) family
{
  return _family;
}

//
// Get the socket type
// 
// @return the type (DSK_STREAM, DSK_DGRAM)
//
- (int) type
{
  return _type;
}

//// Socket option methods

//
// Set a socket option
//
// @param level     the level for the socket option (e.g. SOL_SOCKET)
// @param optname   the name for the socket option
// @param optval    the pointer to the option value
// @param optsize   the size of the option value
// 
// @return success
//

- (BOOL) setSocketOption :(int) level :(int) optname :(void *) optval :(int) optsize
{
  BOOL ok = NO;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    ok = (setsockopt(_socket, level, optname, optval, optsize) == 0);
    
    if (!ok)
      _errno = ERRNO;
  }
  
  return ok;
}

//
// Get a socket option
//
// @param level     the level for the socket option (e.g. SOL_SOCKET)
// @param optname   the name for the socket option
// @param optval    the pointer to the option value
// @param optsize   the size of the option value
// 
// @return success
// 

- (BOOL) getSocketOption :(int) level :(int) optname :(void *) optval :(int) optsize
{
  BOOL ok = NO;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    socklen_t length = optsize;
    
    ok = (getsockopt(_socket, level, optname, optval, &length) == 0);
    
    if (!ok)
      _errno = ERRNO;
  }
  
  return ok;
}

//
// Set the socket blocking
// 
// @param block     should the socket be blocking ?
// 
// @return success
// 

- (BOOL) blocking :(BOOL) block
{
  BOOL ok = NO;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
#ifdef WIN32
    u_long state = (block ? 0 : 1);
    
    ok = (ioctlsocket(_socket, FIONBIO, &state) != SOCKET_ERROR);

    if (!ok)
      _errno = ERRNO;
#else
    int flags;
    
    flags = fcntl(_socket, F_GETFL, 0);

    if (block)
    {
      flags |= O_NONBLOCK;
    }
    else
    {
      flags &= ~O_NONBLOCK;
    }

    ok = (fcntl(_socket, F_SETFL, flags) == 0);

    if (!ok)
      _errno = ERRNO;
#endif
  }

  if (ok)
  {
    _blocking = block;
  }
  
  return ok;
}

//
// Get the socket blocking state
// 
// @return is the socket blocking ?
// 

- (BOOL) blocking
{
  return _blocking;
}

// 
// Set the reuse address socket option
//
// @param reuse    can the address be reused ?
//
// @return success
//

- (BOOL) reuseAddr :(BOOL) reuse
{
  int value = (int) reuse;
  
  return [self setSocketOption :SOL_SOCKET :SO_REUSEADDR :&value :sizeof(value)];
}

//
// Get the reuse address socket option
//
// @return can the address be reused ?
//

- (BOOL) reuseAddr
{
  int value = 0;

  [self getSocketOption :SOL_SOCKET :SO_REUSEADDR :&value :sizeof(value)];
  
  return (value != NO);
}

//
// Set the send buffer size socket option
//
// @param size      the size for the send buffer
//
// @return success
//

- (BOOL) sendBufferSize :(int) size
{
  BOOL ok = NO;
  
  if (size > 0)
  {
    ok = [self setSocketOption :SOL_SOCKET :SO_SNDBUF :&size :sizeof(size)];
  }
  else
    WARNING(DW_INVALID_ARG, "size");
  
  return ok;
}

//
// Get the send buffer size socket option
//
// @return the size
//

- (int) sendBufferSize
{
  int size = 0;

  [self getSocketOption :SOL_SOCKET :SO_SNDBUF :&size :sizeof(size)];
  
  return size;
}

//
// Set the receive buffer size socket option
//
// @param size      the size for the receive buffer
//
// @return success
//

- (BOOL) receiveBufferSize :(int) size
{
  BOOL ok = NO;
  
  if (size > 0)
  {
    ok = [self setSocketOption :SOL_SOCKET :SO_RCVBUF :&size :sizeof(size)];
  }
  else
    WARNING(DW_INVALID_ARG, "size");
  
  return ok;
}

//
// Get the receive buffer size socket option
//
// @return the size
//

- (int) receiveBufferSize
{
  int size = 0;

  [self getSocketOption :SOL_SOCKET :SO_RCVBUF :&size :sizeof(size)];
  
  return size;
}

//
// Set the keep alive socket option
//
// @param keep      should the connnection keep alive ?
//
// @return success
//

- (BOOL) keepAlive :(BOOL) keep
{
  int value = (keep ? 1 : 0);
    
  return [self setSocketOption :SOL_SOCKET :SO_KEEPALIVE :&value :sizeof(value)];
}

//
// Get the keep alive socket option
//
// @return the keep alive setting
//

- (BOOL) keepAlive
{
  int value = NO;

  [self getSocketOption :SOL_SOCKET :SO_KEEPALIVE :&value :sizeof(value)];
  
  return (value != NO);
}

//
// Set the linger time out value
// 
// @param secs      the number of seconds for linger (or 0 for off)
// 
// @return success
//

- (BOOL) linger :(unsigned) secs
{
  struct linger  value;
  
  value.l_onoff  = (secs > 0) ? 1    : 0;
  value.l_linger = (secs > 0) ? secs : 0;
  
  return [self setSocketOption :SOL_SOCKET :SO_LINGER :&value :sizeof(value)];
}

//
// Get the linger time out value
// 
// @return the linger time out value (or 0 for off)
// 

- (unsigned) linger
{
  BOOL ok = NO;
  
  struct linger  value;

  value.l_onoff  = 0;
  value.l_linger = 0;
  
  ok = [self getSocketOption :SOL_SOCKET :SO_LINGER :&value :sizeof(value)];
  
  return ((value.l_onoff) && (ok)) ? value.l_linger : 0;
}


//// Connection methods

//
// Open a socket
// 
// @param family    the socket family (DSA_AF_INET, DSA_AF_UNIX)
// @param type      the socket type (DSK_STREAM, DSK_DGRAM)
// @param protocol  the protocol (see [protocol :name])
// 
// @return success
// 

- (BOOL) open :(int) family :(int) type :(int) protocol
{
  BOOL ok = NO;

  if (_socket != -1)
  {
    [self close];
  }

  // socket() checks the family, type and protocol..

  _type     = type;
  _protocol = protocol;
  _family   = family;
  
  _socket = socket(_family, _type, _protocol);

#ifdef WIN32
  ok = (_socket != INVALID_SOCKET);
#else
  ok = (_socket != -1);
#endif
  
  if (ok)
  {
#ifdef USE_NOSIGPIPE
    int value = 1;
  
    [self setSocketOption :SOL_SOCKET :SO_NOSIGPIPE :&value :sizeof(value)];
#endif
  }
  else
  {
    _socket = -1;
    _errno  = ERRNO;
  }
  
  return ok;
}

//
// Bind the socket to the address (inet:server only, unix:both)
// 
// @param address      the address to bind to
// 
// @return success
// 

- (BOOL) bind :(id <DSocketAddressable>) address
{
  BOOL ok = NO;
  
  if ((address == nil) || ([address family] != _family))
  {
    WARNING(DW_INVALID_ARG, "address");
  }
  else if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    ok = (bind(_socket, [address sockaddr], [address size]) == 0);
    
    if (!ok)
    {
      _address = nil;
      _errno   = ERRNO;
    }
    else
    {
      _address = address;
    }
  }
  
  return ok;
}

//
// Listen for incoming connnections by a server
// 
// @param backlog   the number of pending connection before refuse
// 
// @return success
// 

- (BOOL) listen :(int) backlog
{
  BOOL ok = NO;
  
  if (backlog < 1)
  {
    WARNING(DW_INVALID_ARG, "backlog");
  }
  else if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    ok = (listen(_socket, backlog) == 0);
    
    if (!ok)
    {
      _errno = ERRNO;
    }
  }
  
  return ok;
}

//
// Accept a connection (server only)
// 
// @return the (new) socket object for the connection (or nil for error)
// 

- (DSocket *) accept
{
  DSocket *socket = nil;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    int peer = -1;
    
    struct sockaddr  addr;
    socklen_t        addrlen;
    
    memset(&addr, 0, sizeof(addr));
    
    addrlen = sizeof(addr);
    
    peer = accept(_socket, &addr, &addrlen);

#ifdef WIN32
    if (peer != INVALID_SOCKET)
#else
    if (peer != -1)
#endif
    {
      id <DSocketAddressable> peerAddress = [_address copy];
      
      [peerAddress sockaddr :&addr :addrlen];

      socket = [DSocket alloc];

      [socket init :peer :peerAddress :_type :_protocol];
    }
    else
      _errno = ERRNO;
  }
    
  return socket;
}

//
// Connect a client to a server
// 
// @param address   the address of the server
// 
// @return success
// 

- (BOOL) connect :(id <DSocketAddressable>) address
{
  BOOL ok = NO;

  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((address == nil) || ([address family] != _family))
  {
    WARNING(DW_INVALID_ARG, "address");
  }
  else
  {
    ok = (connect(_socket, [address sockaddr], [address size]) == 0);
  
    if (!ok)
    {
      _errno = ERRNO;
    }
  }

  return ok;
}

//
// Close a socket
// 
// @return success
// 

- (BOOL) close
{
  BOOL ok = YES;
  
  if (_socket != -1)
  {
#ifdef WIN32
    ok = (closesocket(_socket) != SOCKET_ERROR);
#else
    ok = (close(_socket) == 0);
#endif
    
    if (!ok)
    {
      _errno = ERRNO;
    }
    
    _socket = -1;
  }
  
  return ok;
}

//
// Shutdown the connection
// 
// @param what      what part of the connection must be shutdown (DSK_SHUT_RD,DSK_SHUT_WR,DSK_SHUT_RDWR)
// 
// @return success
// 

- (BOOL) shutdown :(int) what
{
  BOOL ok = NO;

  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((what != DSK_SHUT_RD) && (what != DSK_SHUT_WR) && (what != DSK_SHUT_RDWR))
  {
    WARNING(DW_INVALID_ARG, "what");
  }
  else
  {
    ok = (shutdown(_socket, what) == 0);
    
    if (!ok)
      _errno = ERRNO;
  }
  
  return ok;
}


//// Data transfer methods

//
// Send data (UDP)
// 
// @param address   the address to send to
// @param data      the data to be sent
// @param length    the length of the data
// @param flags     the message flags (DSK_MSG.. or 0)
// 
// @return the number of bytes sent (or -1 for error)
// 

- (int) sendto :(id <DSocketAddressable>) address :(void *) data :(int) length :(int) flags
{
  int result = -1;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((address == nil) || ([address family] != _family))
  {
    WARNING(DW_INVALID_ARG, "address");
  }
  else if ((data == NULL) || (length <= 0))
  {
    WARNING(DW_INVALID_ARG, "data/length");
  }
  else if (_type != DSK_DGRAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else
  {
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif

    result = sendto(_socket, data, length, flags, [address sockaddr], [address size]);
  }
  
  return result;
}

//
// Receive data (UDP)
// 
// @param address   the destination for the peer address
// @param length    the length of the data to be received
// @param flags     the receive flags (DSK_MSG.. or 0)
// 
// @return a new DData object (or nil)
// 

- (DData *) recvfrom :(id <DSocketAddressable>) address :(int) length :(int) flags
{
  DData *data = nil;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if (length <= 0)
  {
    WARNING(DW_INVALID_ARG, "length");
  }
  else if (address == nil)
  {
    WARNING(DW_INVALID_ARG, "address");
  }
  else if (_type != DSK_DGRAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else
  {
    int             result = -1;
    struct sockaddr sockaddr;
    socklen_t       addrlen;
    void           *buffer = objc_malloc(sizeof(unsigned char) * length);
    
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif
    
    addrlen = sizeof(sockaddr);
    memset(&sockaddr, 0, addrlen);
    
    result = recvfrom(_socket,  buffer, length, flags, &sockaddr, &addrlen);
    
    if (result < 0)
    {
      _errno = ERRNO;
    }
    else
    {
      [address sockaddr :&sockaddr :addrlen];
      
      data = [DData alloc];
      
      [data init :buffer :result];
    }
    
    objc_free(buffer);
  }
  
  return data;
}

//
// Send data (TCP)
// 
// @param data      the data to be sent
// @param length    the length of the data
// @param flags     the message flags (DSK_MSG.. or 0)
// 
// @return the number of bytes sent (or -1 for error)
// 

- (int) send :(void *) data :(int) length :(int) flags
{
  int result = -1;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((data == NULL) || (length <= 0))
  {
    WARNING(DW_INVALID_ARG, "data/length");
  }
  else if (_type != DSK_STREAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else
  {
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif

    result = send(_socket, data, length, flags);
    
    if (result < 0)
    {
      _errno = ERRNO;
    }
  }
  
  return result;
}


//
// Receive data (TCP)
// 
// @param length    the length of the data to be received
// @param flags     the receive flags (DSK_MSG.. or 0)
// 
// @return a new DData object (or nil)
// 
- (DData *) recv :(int) length :(int) flags
{
  DData *data = nil;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if (length <= 0)
  {
    WARNING(DW_INVALID_ARG, "length");
  }
  else if (_type != DSK_STREAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else
  {
    int result   = -1;
    void *buffer = objc_malloc(sizeof(unsigned char) * length);
 
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif
    
    result = recv(_socket, buffer, length, flags);
    
    if (result < 0)
    {
      _errno = ERRNO;
    }
    else
    {
      data = [DData alloc];
      
      [data init :buffer :result];
    }
    
    objc_free(buffer);
  }
  
  return data;
}


//// Text transfer methods

//
// Send text (UDP)
// 
// @param address   the address to send to
// @param cstring   the cstring to be sent
// @param flags     the message flags (DSK_MSG.. or 0)
// 
// @return the number of bytes sent (or -1 for error)
// 

- (int) sendto :(id <DSocketAddressable>) address :(const char *) cstring :(int) flags
{
  int result = -1;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((address == nil) || ([address family] != _family))
  {
    WARNING(DW_INVALID_ARG, "address");
  }
  else if ((cstring == NULL) || (*cstring == EOS))
  {
    WARNING(DW_INVALID_ARG, "cstring");
  }
  else if (_type != DSK_DGRAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else
  {
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif

    result = sendto(_socket, cstring, strlen(cstring), flags, [address sockaddr], [address size]);
  }
  
  return result;
}


//
// Receive text (UDP)
// 
// @param dest      the destination text object
// @param address   the destination for the peer address
// @param length    the length of the data to be received
// @param flags     the receive flags (DSK_MSG.. or 0)
// 
// @return the number of bytes received (or -1)
// 

- (int) recvfrom :(DText *) dest :(id <DSocketAddressable>) address :(int) length :(int) flags
{
  int result = -1;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if (length <= 0)
  {
    WARNING(DW_INVALID_ARG, "length");
  }
  else if (address == nil)
  {
    WARNING(DW_INVALID_ARG, "address");
  }
  else if (_type != DSK_DGRAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else if (dest == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "dest");
  }
  else
  {
    struct sockaddr sockaddr;
    socklen_t       addrlen;
    unsigned char  *buffer = objc_malloc(sizeof(unsigned char) * length);
    
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif
    
    addrlen = sizeof(sockaddr);
    memset(&sockaddr, 0, addrlen);
    
    result = recvfrom(_socket,  buffer, length, flags, &sockaddr, &addrlen);
    
    if (result < 0)
    {
      _errno = ERRNO;
    }
    else
    {
      int i;
      
      unsigned char *pntr = buffer;
      
      [address sockaddr :&sockaddr :addrlen];
      
      [dest clear];
      
      for (i = 0; i < result; i++)
      {
        if ((*pntr > 0) && (*pntr <= 127))
        {
          [dest push :*pntr];
        }
        
        pntr++;
      }
    }
    
    objc_free(buffer);
  }
  
  return result;
}

//
// Send text (TCP)
// 
// @param cstring   the c-string to be sent
// @param flags     the message flags (DSK_MSG.. or 0)
// 
// @return the number of bytes sent (or -1 for error)
// 

- (int) send :(const char *) cstring :(int) flags
{
  int result = -1;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((cstring == NULL) || (*cstring == EOS))
  {
    WARNING(DW_INVALID_ARG, "cstring");
  }
  else if (_type != DSK_STREAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else
  {
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif

    result = send(_socket, cstring, strlen(cstring), flags);
    
    if (result < 0)
    {
      _errno = ERRNO;
    }
  }
  
  return result;
}

//
// Receive text (TCP)
// 
// @param dest     the destination text object
// @param length   the length of the data to be received
// @param flags    the receive flags (DSK_MSG.. or 0)
// 
// @return the number of characters received (or -1 for error)
// 

- (int) recv :(DText *) dest :(int) length :(int) flags
{
  int result = -1;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if (length <= 0)
  {
    WARNING(DW_INVALID_ARG, "length");
  }
  else if (_type != DSK_STREAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else if (dest == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "dest");
  }
  else
  {
    unsigned char *buffer = (unsigned char *) objc_malloc(sizeof(unsigned char) * length);
 
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif
    
    result = recv(_socket, buffer, length, flags);
    
    if (result < 0)
    {
      _errno = ERRNO;
    }
    else
    {
      int i;
      
      unsigned char *pntr = buffer;
      
      [dest clear];
      
      for (i = 0; i < result; i++)
      {
        if ((*pntr > 0) && (*pntr <= 127))
        {
          [dest push :*pntr];
        }
        
        pntr++;
      }
    }
    
    objc_free(buffer);
  }
  
  return result;
}


//
// Receive (ASCII) data (TCP) till an end of line
// 
// @param dest      the destination for the received data
// @param eofl      the end of line string
// @param length    the maximum length of the data to be received (or -1 for no maximum)
// @param flags     the receive flags (DSK_MSG.. or 0)
// 
// @return the number of bytes received (or -1 for error)
// 
- (long) recv :(DText *) dest :(const char *) eofl :(long) length :(int) flags
{
  BOOL err   = YES;
  
  if (_socket == -1)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else if ((length == 0) || (length < -1))
  {
    WARNING(DW_INVALID_ARG, "length");
  }
  else if (_type != DSK_STREAM)
  {
    WARNING(DW_UNEXPECTED_ERROR, "invalid socket type");
  }
  else if (dest == nil)
  {
    WARNING(DW_INVALID_ARG, "dest");
  }
  else if ((eofl == NULL) || (*eofl == EOS))
  {
    WARNING(DW_INVALID_ARG, "eofl");
  }
  else
  {
    int  index = 0;
    BOOL done  = NO;
    int  result;
    
    err = NO;

    [dest clear];

    if (length > 0)
    {
      [dest size :length];
    }
    
#ifndef WIN32
    flags |= MSG_NOSIGNAL;
#endif

    while (!done)
    {
      unsigned char ch;
      
      result = recv(_socket, &ch, 1, flags);

      
      if (result < 0)
      {
        _errno = ERRNO;
        
        done = YES;
        err  = YES;
      }
      else if ((result == 1) && (ch > 0) && (ch <= 127))
      {
        if (ch != eofl[index])
        {
          if (index > 0)               // this is not the end of the line, restore the received data
          {
            int i = 0;
            
            while (i < index)
            {
              [dest push :eofl[i++]];
            }
            
            index = 0;
          }

          [dest push :ch];
        }
        else
        {
          index++;
          
          done = (eofl[index] == EOS);
        }
      }
      else
      {
        done = YES;
      }
    }
    
    err = (([dest length] == 0) && (index == 0));
  }
  
  return err ? -1 : [dest length];
}

@end

/*===========================================================================*/

